package com.example.user.service.permission;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.framework.common.exception.ServiceException;
import com.example.user.constants.ErrorCodeConstants;
import com.example.user.controller.permission.vo.menu.MenuSaveVO;
import com.example.user.dal.dataobject.permission.MenuDO;
import com.example.user.dal.mysql.permission.MenuMapper;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

import static com.example.user.dal.dataobject.permission.MenuDO.ID_ROOT;

@Slf4j
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, MenuDO> implements MenuService {

    @Resource
    private MenuMapper menuMapper;

    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private PermissionService permissionService;

    @Override
    public Long createMenu(MenuSaveVO createReqVO) {
        // 校验父菜单存在
        validateParentMenu(createReqVO.getParentId(), null);
        // 校验菜单（自己）
        validateMenu(createReqVO.getParentId(), createReqVO.getName(), null);
        // 插入数据库
        MenuDO menu = MenuDO.builder()
                .name(createReqVO.getName())
                .permission(createReqVO.getPermission())
                .parentId(createReqVO.getParentId())
                .component(createReqVO.getComponent())
                .componentName(createReqVO.getComponentName())
                .status(createReqVO.getStatus())
                .visible(createReqVO.getVisible())
                .build();
        menuMapper.insert(menu);
        // 返回
        return menu.getId();
    }

    @Override
    public void updateMenu(MenuSaveVO updateReqVO) {
        // 校验更新的菜单是否存在
        if (menuMapper.selectById(updateReqVO.getId()) == null) {
            throw ServiceException.of(ErrorCodeConstants.MENU_NOT_EXISTS);
        }
        // 校验父菜单存在
        validateParentMenu(updateReqVO.getParentId(), updateReqVO.getId());
        // 校验菜单（自己）
        validateMenu(updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getId());

        // 更新到数据库
        MenuDO updateObj = BeanUtil.toBean(updateReqVO, MenuDO.class);
        menuMapper.updateById(updateObj);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    // allEntries 清空所有缓存，因为此时不知道 id 对应的 permission 是多少。直接清理，简单有效
    @CacheEvict(value = "permission_menu_ids", allEntries = true)
    public void deleteMenu(Long id) {
        // 校验是否还有子菜单
        if (menuMapper.selectCount(Wrappers.<MenuDO>lambdaQuery()
                .eq(MenuDO::getParentId, id)) > 0) {
            throw ServiceException.of(ErrorCodeConstants.MENU_EXISTS_CHILDREN);
        }
        // 校验删除的菜单是否存在
        if (menuMapper.selectById(id) == null) {
            throw ServiceException.of(ErrorCodeConstants.MENU_NOT_EXISTS);
        }
        // 标记删除
        menuMapper.deleteById(id);
        // 删除授予给角色的权限
        permissionService.processMenuDeleted(id);
    }

    @Override
    public MenuDO getMenu(Long id) {
        return menuMapper.selectById(id);
    }

    /**
     * 校验父菜单是否合法
     * <p>
     * 1. 不能设置自己为父菜单
     * 2. 父菜单不存在
     *
     * @param parentId 父菜单编号
     * @param childId  当前菜单编号
     */
    void validateParentMenu(Long parentId, Long childId) {
        if (parentId == null || ID_ROOT.equals(parentId)) {
            return;
        }
        // 不能设置自己为父菜单
        if (parentId.equals(childId)) {
            throw ServiceException.of(ErrorCodeConstants.MENU_PARENT_ERROR);
        }
        MenuDO menu = menuMapper.selectById(parentId);
        // 父菜单不存在
        if (menu == null) {
            throw ServiceException.of(ErrorCodeConstants.MENU_PARENT_NOT_EXISTS);
        }
    }

    /**
     * 校验菜单是否合法
     * <p>
     * 1. 校验相同父菜单编号下，是否存在相同的菜单名
     *
     * @param name     菜单名字
     * @param parentId 父菜单编号
     * @param id       菜单编号
     */
    @VisibleForTesting
    void validateMenu(Long parentId, String name, Long id) {
        MenuDO menu = menuMapper.selectOne(Wrappers.<MenuDO>lambdaQuery()
                .eq(MenuDO::getParentId, Optional.ofNullable(parentId).orElse(ID_ROOT))
                .eq(MenuDO::getName, name));
        if (menu == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的菜单
        if (id == null) {
            throw ServiceException.of(ErrorCodeConstants.MENU_NAME_DUPLICATE);
        }
        if (!menu.getId().equals(id)) {
            throw ServiceException.of(ErrorCodeConstants.MENU_NAME_DUPLICATE);
        }
    }

}
